Repository navigation
[MoE] Use the sorted fused-MoE path for host-resident expert weights - #62054
Conversation
At small batch (num_tokens * top_k * 4 <= num_experts) the Triton fused-MoE path skips moe_align_block_size and gives every (token, expert) pair its own block, so an expert routed to several tokens is read once per token. That is cheap for weights in device memory, but with UVA weight offloading (--offload-backend uva) the experts are read from host memory in place, and every repeated read crosses PCIe. Take the sorted path when the expert weights carry the UVA offload marker, so each routed expert is read once per step. Device-resident weights keep the naive path. Measured with Qwen3-30B-A3B FP8 on one RTX 4090 (PCIe 4.0) with 16 GiB of experts offloaded: +7/+10/+15-19% output throughput at 2/3/4 requests in flight on natural-text prompts, unchanged at 1, 8 and 16, identical output. Assisted-by: Claude Opus 5.5 Signed-off-by: Andrew Goh <andrew.goh@zillion.network>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
|
Do you have any benchmarks for nvidia hardware? |
Yes, all the numbers above are on RTX 4090s (Ada, PCIe 4.0 x16) with CUDA, on a 2-socket EPYC host: 30B on If you meant datacenter GPUs, I don't currently have access to any. The change only affects host-resident The motivation is VRAM-limited setups like mine (consumer/workstation cards, or fewer GPUs than the model |
Sorry, for some reason I thought this was for AMD. The changes LGTM. |
|
✅ @mushroomcarbon, CI is now available for this PR.
|
|
/ci run |
|
✅ Triggered Buildkite CI #96085 for commit ℹ️ This PR is 214 commits behind upstream |
|
Note for reviewers: the remaining CI failures are all ColPali ( |
|
/ci retry |
|
✅ Triggered Buildkite CI #96117 for commit |
|
/ci run |
|
✅ Triggered Buildkite CI #96453 for commit ℹ️ This PR is 1 commit behind upstream |
Overview
At small batch (
num_tokens * top_k * 4 <= num_experts, e.g. up to 4 tokens for Qwen3 MoE) the Triton fused-MoE path skipsmoe_align_block_sizeand gives every (token, expert) pair its own block (#29354, #39016). That saves the alignment kernel when weights are in device memory. With UVA weight offloading (--offload-backend uva --cpu-offload-params experts) the expert weights are read from host memory in place, and an expert routed to several tokens is then read over PCIe once per token. This PR takes the sorted path when the expert weights carry the UVA offload marker, so each routed expert is read once per step. Device-resident weights keep the naive path.Claims
On RTX 4090 (PCIe 4.0 x16), natural-text chat requests, 128 output tokens:
Validation
pytest tests/kernels/moe/test_moe.py -k "host_resident_weights or naive_block_assignment": 44 passed on an RTX 4090, on the original base and again after rebasing onto current main (nightly wheel of the base commit, patched files applied). The newtest_host_resident_weights_take_sorted_pathruns UVA-mapped weights (and device weights as the control) at 1/2/4 tokens through both the modularTritonExpertspath and the legacyfused_moe, asserts thatmoe_align_block_sizeis called exactly when the weights are host-resident, and compares the output withtorch_moe(inputs scaled to O(1) outputs,atol=rtol=2e-2). Without the change its 6 host-resident cases fail.Every applicable pre-commit hook passes: ruff check/format, mypy 3.10–3.13,
check_torch_cuda, test tethering, typos, SPDX, lazy imports, forbidden imports, boolean context manager.End to end, base vLLM against this PR, one
vllm serve, requests from a client sending natural-language chat prompts (32 topics × 8 tasks, temperature 0.7, a fixed seed per request,ignore_eos):Reproducible with vLLM's own benchmark (
vllm bench serve --dataset-name sharegpt --sharegpt-output-len 128 --ignore-eos --num-prompts N --max-concurrency c --seed 1, ShareGPT_V3_unfiltered_cleaned_split; N = 8 for c < 4, else 2c), 30B on one GPU with 16 GiB offloaded, two starts each:--max-concurrencyServer commands
The table above the benchmark used our own client sending natural-language chat prompts (32 topics × 8 tasks, temperature 0.7, fixed seed per request, 128 tokens with
ignore_eos; N requests at concurrency c, N = 8 for c < 4, else 2c; one unrecorded warm-up per level).Details
Why. With experts in host memory, a decode step's cost is dominated by the bytes it reads over PCIe, and on the naive path an expert routed to several of the step's tokens is read once per token. The sorted path reads each routed expert once per step. How much that saves depends on how often a step's tokens share experts: on natural-text prompts the gain is in the Validation table; on random-token prompts with greedy decoding, whose routing is far more repetitive, the same change doubles throughput at 4 requests in flight. We report natural text.
Implementation.
_prepare_expert_assignmenttakesweights_host_resident(defaultFalse), which disables the naive block assignment.TritonExperts.applyand the legacyfused_experts_implpass_weights_host_resident(w1, w2): true when either weight has_vllm_is_uva_offloaded, which the UVA offloader sets (anddevice_loading_contextre-sets afterprocess_weights_after_loading).w1/w2are the layer's own parameters on this path, so it is a getattr per forward inside themoe_forwardcustom op; the result depends only on the parameters, so CUDA-graph capture per size is unaffected. "Either", because UVA offloads per parameter and a budget cut-off can leave w13 on the host and w2 on the device.TritonWNA16Expertsalways align; the prefetch offloader copies weights to the device before compute and never sets the marker. Two model-specific callers of_prepare_expert_assignment(models/k2_horizon.py,models/dots3_note/nvidia/vision_moe.py) do not pass the flag and keep today's behaviour; they can opt in separately.replace_parameterinprocess_weights_after_loadinginstalls a new Parameter without the marker, anddevice_loading_contextre-marks it only afterwards, so a setup-time check would read False.Related. No open or merged PR makes this change (searched 2026-10-07, rechecked 2026-10-16). The naive path came from #29354 and #39016; #34920 (closed, stale) proposed making it tunable from configs; #55268 (open) warms up Triton MoE variants per batch size, and this PR changes which variant host-resident weights use at ≤ 4 tokens. #57215 (open) keeps MoE experts offloaded during weight post-processing on XPU, the marker lifecycle this PR relies on; #56177 (open) is a shared GPU expert pool. RFC #38256 discusses expert caches for offloaded MoE, where a comment reports the same effect with a GPU-side slot buffer (the naive path stops sharing an expert's weight tile between tokens at batch ≥ 2). #37190, #57794 and #57943 decide which experts stay in VRAM; this PR is independent of that and helps whichever experts remain on the host.
AI assistance. Claude Opus 5.5 assisted with the code, tests, benchmarks and this description; the tests and benchmarks above were run on my hardware. I reviewed every changed line and the results, and can answer for it; the commit carries an
Assisted-by:trailer.Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.